package com.example.core.filter.flowctl.impl;

import com.alibaba.fastjson.JSON;
import com.example.common.config.Rule;
import com.example.common.enums.ResponseCode;
import com.example.common.exception.LimitedException;
import com.example.core.filter.flowctl.IFlowControlRule;
import com.example.core.filter.flowctl.limiter.GuavaCountLimiter;
import com.example.core.filter.flowctl.limiter.RedisCountLimiter;
import com.example.core.redis.JedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.example.common.constants.FilterConst.*;

/**
 * @Author Peng Yisheng
 * @Date 2024/12/6 20:25
 * @Description 路径限流规则实现
 */
@Slf4j
public class FlowControlByPathRule implements IFlowControlRule {
    private String serviceId;

    private String path;

    private RedisCountLimiter redisCountLimiter;

    private static final String LIMIT_MESSAGE = "您的请求过于频繁,请稍后重试";

    public FlowControlByPathRule(String serviceId, String path, RedisCountLimiter redisCountLimiter) {
        this.serviceId = serviceId;
        this.path = path;
        this.redisCountLimiter = redisCountLimiter;
    }

    private static ConcurrentHashMap<String, FlowControlByPathRule> servicePathMap = new ConcurrentHashMap<>();

    public static FlowControlByPathRule getInstance(String serviceId, String path) {
        String key = String.format("%s.%s", serviceId, path);
        FlowControlByPathRule flowControlByPathRule = servicePathMap.get(key);
        if (flowControlByPathRule == null) {
            flowControlByPathRule = new FlowControlByPathRule(serviceId, path, new RedisCountLimiter(new JedisUtil()));
            servicePathMap.put(key, flowControlByPathRule);
        }
        return flowControlByPathRule;
    }

    public void doFlowControlFilter(Rule.FlowControlConfig flowControlConfig, String serviceId) {
        if (flowControlConfig == null || StringUtils.isEmpty(serviceId) || StringUtils.isEmpty(flowControlConfig.getConfig())) {
            return;
        }
        // 获得当前路径对应的流控配置
        Map<String, Integer> configMap = JSON.parseObject(flowControlConfig.getConfig(), Map.class);
        //判断是否包含流控规则
        if (!configMap.containsKey(FLOW_CTL_LIMIT_DURATION) || !configMap.containsKey(FLOW_CTL_LIMIT_PERMITS)) {
            return;
        }
        // 得到流控时间和时间内限制次数
        double duration = configMap.get(FLOW_CTL_LIMIT_DURATION);
        double permits = configMap.get(FLOW_CTL_LIMIT_PERMITS);

        // 当前请求是否触发流控标志位
        boolean flag = false;
        String key = String.format("%s.%s", serviceId, path);
        // 如果是分布式项目 那么我们就需要使用Redis来实现流控  单机则可以直接使用Guava
        // TODO：这里Limiter实例缓存无法感知配置变更，需要修复
        if (FLOW_CTL_MODEL_DISTRIBUTED.equalsIgnoreCase(flowControlConfig.getModel())) {
            flag = redisCountLimiter.doFlowControl(key, (int) permits, (int) duration);
        } else {
            // 单机版限流 直接用Guava
            GuavaCountLimiter guavaCountLimiter = GuavaCountLimiter.getInstance(serviceId, flowControlConfig);
            if (guavaCountLimiter == null) {
                throw new RuntimeException("获取单机限流工具类为空");
            }
            // 获取许可
            flag = guavaCountLimiter.acquire();
        }
        if (!flag) {
            throw new LimitedException(ResponseCode.FLOW_CONTROL_ERROR);
        }
    }

}
